// Copyright (C) 2010 and onwards Noam Shazeer and Georges Harik
// Pieces of this code may be licenced under the Apache License, version 2.0 
//   from Harik Shazeer Labs

#include "thing.h"
#include "interpret.h"

map<string, Thing > Thing::string_thing_;
map<Thing , string> Thing::thing_string_;

// These are set before their use in the global allocator
uint64 G_BOX_INDEX_SPACE = 0, G_BOX_INDEX_MASK = 0; 

// Inited by the global allocator but needed here
Box ** G_BOX_INDEX = NULL;

// ********** THING **********

uint64 Thing::DirectArrayID() const {
  return ((Box *) data_)->id_; }

Box * Thing::MaybeGetBox() const {
  if (GetBasicType() == DIRECT_ARRAY) return (Box*) data_;
  Box ** bpp = GetBoxPointerPointer();
  if (bpp) return *bpp;
  return NULL;
}

Box * Thing::GetBox() const {
  Box *b = MaybeGetBox();
  CHECK(b);
  return b;
}

// This should only be called for indirect things
Box ** Thing::GetBoxPointerPointer() const {
  if (!IsIndirect()) {
    cout << "::GetBoxPointerPointer " << ToString(*this) << endl;
    CHECK(false);
  }
  for (uint64 step=0; true; step++) {
    Box ** pp = BoxResolution(data_, step);
    Box *p = *pp;
    if (!p) return NULL;
    if (p->id_ == data_) return pp;
  }  
}

bool Thing::IsString() const {
  if (!IsArray()) return false;
  Box * b = GetBox();
  for (uint64 i=0; i<b->ArraySize(); i++) 
    if (b->Ref(i).GetType() != CHAR) return false;
  return true;
}

Thing & Thing::Ref(int i) { return GetBox()->Ref(i); }
uint64 Thing::ArraySize() const{ return GetBox()->ArraySize(); }
uint64 Thing::NodeCount() const {
  if (GetType() == IMMUTABLE_ARRAY) return GetBox()->node_count_;
  return 1; }

uint64 Thing::MakeArrayID(Thing *contents, uint64 length) {
  uint64 h = ArrayContentHash(contents, length);
  return MakeID(INDIRECT_ARRAY, h);
}

// This binds names to unboxed things
Thing Thing::BindUnboxed(ThingType tt, int value, string name) {
  // cout << "BindUnboxed " << name << endl;
  Thing t = MakeUnboxed(tt, value);
  if (string_thing_ % name) {
    cout << name << " already in string_thing_" << endl;
    CHECK(false);
  }
  string_thing_[name] = t;
  thing_string_[t] = name;
  return t; }

Thing Thing::NameToUnboxedThing(string name) {
  Thing * ret = (string_thing_ % name);
  if (ret) return *ret; 
  return 0; }


// The specials are allocated in a dense space
Thing Thing::MakeSpecial(string name) {
  static int next = 0;
  return BindUnboxed(SPECIAL, next++, name); }

// ********** BOX **********

// insert a box into the index.
uint64 Box::Index() {
  CHECK(id_);
  for (uint64 step=0; true; step++) {
    Box ** pp = BoxResolution(id_, step);
    Box *p = *pp;
    if (!p) {
      // we found a place to insert it. 
      if (__sync_bool_compare_and_swap(pp, (Box *)NULL, this)) return step;
      // Someone else inserted something here
      p = (*pp); CHECK(p);
    }
    if (p->id_ == id_) {
      bool differ = !BoxesHaveSameIDs(this, p);
      if (differ) {
	cout << "*** ::Index CollisionException ***" << endl;
	throw CollisionException();
      } 
      return step;
    }
  }
}

// returns the old value of the flag
bool Box::AtomicSetFlag(uint64 flag) {
  if (GetFlag(flag)) return true;
  uint64 old_val = __sync_fetch_and_or(&info_, flag);
  return old_val & flag;
}

bool Box::IsEqualToImmutableArray(Thing *contents, uint64 length) {
  if (IsMutable()) return false;
  if (ArraySize() != length) return false;
  for (uint64 i=0; i<length; i++)
    if (contents[i] != Ref(i)) return false;
  return true;
}

void Box::InitImmutableArray(uint64 id, Thing *data, uint64 length) {
  ZeroOut();
  SetType(IMMUTABLE_ARRAY);
  SetLength(length);
  Thing * dest = (Thing*) ImmutableArrayData();
  memcpy(dest, data, length * sizeof(Thing));
  node_count_ = 1;
  for (uint64 i=0; i<length; i++) {
    uint64 cnc = data[i].NodeCount();
    node_count_ += cnc;
    if ( (cnc ==0) || (node_count_ >= (1ull << 60))) {
      node_count_ = 0; break;
    }
  }
  id_ = id;
}

void Box::InitMutableArray() {
  ZeroOut();
  SetType(MUTABLE_ARRAY);
  SetLength(0);
  marray_data__ = NULL;
  marray_capacity__ = 0;
  id_ = (uint64)this;
}

void Box::InitSymbol(uint64 id, const string &data) {
  ZeroOut();
  SetType(SYMBOL);
  SetLength(data.size());
  for (uint64 i=0; i<data.size(); i++)
    Ref(i) = Thing::MakeCharacter(data[i]);
  node_count_ = 1;
  id_ = id;
  uint64 check_id = Thing::MakeID(SYMBOL, Fingerprint(data));
  CHECK(id == check_id);
}

bool BoxesHaveSameIDs(Box *b1, Box *b2) {
  if (b1->GetLength() != b2->GetLength()) return false;
  for (uint64 i=0; i<b1->GetLength(); i++) {
    if (b1->Ref(i).ID() != b2->Ref(i).ID()) return false;
  }
  return true;
}

int64 VoidToInt64H(const void * th) {
  Thing th2 = *((Thing*) th);
  uint64 id = th2.ID();
  return id;
}

// Sorting Marrays
int CompareThingH(const void * t1, const void *t2) {
  int64 i1 = VoidToInt64H(t1);
  int64 i2 = VoidToInt64H(t2);
  if (i1 < i2) return -1;
  if (i2 < i1) return 1;
  return 0;
}

void SortMArray(int64 width, Thing a) {
  Box * b = a.GetBox();
  uint64 a_size = b->ArraySize();
  if (a_size == 0) return;
  uint64 n = a_size / width;
  Thing * data_start = &(b->Ref(0));
  qsort(data_start, n, width * sizeof(Thing), CompareThingH);
}

// Conversion to strings
string ToString(Thing t) {
  ostringstream ostr;
  ostr << t;
  return ostr.str();
}

string ToStringShort(Thing t) {
  string ret = ToString(t);
  if (ret.size() > 600) 
    ret = ret.substr(0, 600) + "...";
  return ret;
}

string StringToCPPString(Thing t) {
  if (!t.IsString()) return ""; 
  string ret = "";
  Box * b = t.GetBox();
  for (uint64 i=0; i<b->ArraySize(); i++) 
    ret += b->Ref(i).GetChar();
  return ret;
}

void BoxListDisplay(Box *head) {
  cout << "BoxListDisplay " << endl;
  for (Box *p=head; p != BoxListEnd(); p = p->link_) 
    cout << "   " << p->LocalBoxToThing() << endl;
}

bool BoxListPush(Box **head, Box *b) {
  if (b->link_) return false;
  b->link_ = *head;
  *head = b;
  return true;
}

Box * BoxListPop(Box **head) {
  Box * ret = *head;
  *head = ret->link_;
  ret->link_ = NULL;
  return ret;
}

// removes the head of the list and sticks it in after target.
void BoxListShoveHeadAfter(Box **head, Box * target) {
  Box * b = BoxListPop(head);
  CHECK(b != target);
  b->link_ = target->link_;
  target->link_ = b;
}

